package chapter9
{
	import com.adobe.utils.*;
	
	import flash.display.*;
	import flash.display3D.*;
	import flash.display3D.textures.*;
	import flash.events.*;
	import flash.filters.GlowFilter;
	import flash.geom.*;
	import flash.media.Sound;
	import flash.text.*;
	import flash.utils.*;
	
	import utils.DisplayMolehill;
	import utils.Game_actor;
	import utils.Game_actorpool;
	import utils.Game_input;
	import utils.Game_particlesystem;
	import utils.Game_timer;
	import utils.IRender;
	import utils.newclass.Game_input;
	
	public class Chapter9Demp extends DisplayMolehill implements IRender
	{
		// the current state of the game (0 = title screen)
		private const state_titlescreen:int = 0;
		private const state_playing:int = 1;
		private var game_state:int = state_titlescreen;
		private var titlescreen_tf:TextField;
		
		// handles all timers for us
		private var gametimer:Game_timer;
		// handles keyboard and mouse inputs
		private var gameinput:Game_input;
		// nice close behind chase camera
		private var chase_camera_xangle:Number = -5;
		private var chase_camera_distance:Number = 4;
		private var chase_camera_height:Number = 2;
		
		// all known entities in the world
		private var chase_camera:Molehill_entity
		private var props:Vector.<Molehill_entity>;
		private var player:Game_actor;
		private var enemies:Game_actorpool;
		private var player_bullets:Game_actorpool;
		private var enemy_bullets:Game_actorpool;
		private var gamelevel:Game_actorpool;
		
		// used for our particle system demo
		private var next_shoot_time:uint = 0;
		private var shoot_delay:uint = 200;
		private var invulnerability_time_left:int = 0;
		private var invulnerability_ms_when_hit:int = 3000;
		
		private var explo:Molehill_particle;
		private var particle_system:Game_particlesystem;
		// reusable entity pointer (for speed and to avoid GC)
		private var entity:Molehill_entity;
		private var actor:Game_actor;
		// we want to remember these for use in game_step()
		private var asteroids1:Molehill_entity;
		
		
		private var asteroids2:Molehill_entity;
		private var asteroids3:Molehill_entity;
		private var asteroids4:Molehill_entity;
		private var engine_glow:Molehill_entity;
		private var sky:Molehill_entity;
		
		// used by game_step()
		// how fast we move forward in units per ms
		private const fly_speed:Number = 0.35;
		// how fast we move up/down/left/right units per ms
		private const move_speed:Number = 0.01;
		// a slow orbit of the asteroids in deg per ms
		private const asteroid_rotation_speed:Number = 0.001;
		// force the ship to stay within bounds (0 = no restriction)
		private var player_maxx:Number = 32;
		private var player_maxy:Number = 8;
		private var player_maxz:Number = 0;
		private var player_minx:Number = -32;
		private var player_miny:Number = 0.1;
		private var player_minz:Number = 0;
		private var level_complete_player_minz:Boolean = true;
		
		// used by the GUI
		private var fps_last:uint = getTimer();
		private var fps_ticks:uint = 0;
		private var fps_tf:TextField;
		private var score_tf:TextField;
		private var health_tf:TextField;
		private var score:uint = 0;
		private var combo:uint = 0;
		private var combo_best:uint = 0;
		private var scene_polycount:uint = 0;
		// used for the health bar display
		private var health_text_0:String = '';
		private var health_text_1:String =
			'| | | | |';
		private var health_text_2:String =
			'| | | | | | | | | | |';
		private var health_text_3:String =
			'| | | | | | | | | | | | | | | | | |';
		// the 3d graphics window on the stage
		private var context3D:Context3D;
		// the compiled shader used to render our meshes
		private var shaderProgram1:Program3D;
		// matrices that affect the mesh location and camera angles
		private var projectionmatrix:PerspectiveMatrix3D =
			new PerspectiveMatrix3D();
		private var viewmatrix:Matrix3D = new Matrix3D();
		
		// Font used by the GUI
		[Embed (source = 'art/gui_font.ttf',
				embedAsCFF = 'false',
				fontFamily = 'gui_font',
				mimeType = 'application/x-font-truetype',
				unicodeRange='U+0020-U+002F, U+0030-U+0039, U+003A-U+0040, U+0041-U+005A, U+005B-U+0060, U+0061-U+007A, U+007B-U+007E')]
		private const GUI_FONT:Class;
		
		/* TEXTURES: Pure AS3 and Flex version:
		* if you are using Adobe Flash CS5
		* comment out the following: */
		[Embed (source = "art/spaceship_texture.jpg")]
		private var playerTextureBitmap:Class;
		private var playerTextureData:Bitmap = new playerTextureBitmap();
		[Embed (source = "art/terrain_texture.jpg")]
		private var terrainTextureBitmap:Class;
		private var terrainTextureData:Bitmap = new terrainTextureBitmap();
		[Embed (source = "art/craters.jpg")]
		private var cratersTextureBitmap:Class;
		private var cratersTextureData:Bitmap = new cratersTextureBitmap();
		[Embed (source = "art/sky.jpg")]
		private var skyTextureBitmap:Class;
		private var skyTextureData:Bitmap = new skyTextureBitmap();
		[Embed (source = "art/engine.jpg")]
		private var puffTextureBitmap:Class;
		private var puffTextureData:Bitmap = new puffTextureBitmap();
		[Embed (source = "art/particle1.jpg")]
		private var particle1data:Class;
		private var particle1bitmap:Bitmap = new particle1data();
		[Embed (source = "art/particle2.jpg")]
		private var particle2data:Class;
		private var particle2bitmap:Bitmap = new particle2data();
		[Embed (source = "art/particle3.jpg")]
		private var particle3data:Class;
		private var particle3bitmap:Bitmap = new particle3data();
		[Embed (source = "art/particle4.jpg")]
		private var particle4data:Class;
		private var particle4bitmap:Bitmap = new particle4data();
		// new enemies
		[Embed (source = "art/sentrygun.jpg")]
		private var sentrygunTextureData:Class;
		private var sentrygunTextureBitmap:Bitmap = new sentrygunTextureData();
		[Embed (source = "art/boss.jpg")]
		private var bossTextureData:Class;
		private var bossTextureBitmap:Bitmap = new bossTextureData();
		// tech slabs and space stations
		[Embed (source = "art/hulltech.jpg")]
		private var techTextureData:Class;
		private var techTextureBitmap:Bitmap = new techTextureData();
		// GUI bitmap overlays
		[Embed (source = "art/title_screen.png")]
		private var title_screen_data:Class;
		private var title_screen:Bitmap = new title_screen_data();
		[Embed (source = "art/hud_overlay.png")]
		private var hud_overlay_data:Class;
		private var hud_overlay:Bitmap = new hud_overlay_data();
		// sounds and music
		[Embed (source = "art/sfxmusic.mp3")]
		public var intro_mp3:Class;
		public var intro_sound:Sound = (new intro_mp3) as Sound;
		[Embed (source = "art/sfxblast.mp3")]
		public var blast_mp3:Class;
		public var blast_sound:Sound = (new blast_mp3) as Sound;
		[Embed (source = "art/sfxgun.mp3")]
		public var gun_mp3:Class;
		public var gun_sound:Sound = (new gun_mp3) as Sound;
		
		// The Molehill Textures that use the above
		private var playerTexture:Texture;
		private var terrainTexture:Texture;
		private var cratersTexture:Texture;
		private var skyTexture:Texture;
		private var puffTexture:Texture;
		// new textures used by our particle system
		private var particle1texture:Texture;
		private var particle2texture:Texture;
		private var particle3texture:Texture;
		private var particle4texture:Texture;
		// new enemies
		private var sentryguntexture:Texture;
		private var bosstexture:Texture;
		// tech slabs and space stations
		private var techtexture:Texture;
		
		// the player - 142 polygons
		[Embed (source = "art/spaceship.obj",mimeType = "application/octet-stream")]
		private var my_obj_data5:Class;
		// the engine glow - 336 polygons
		[Embed (source = "art/puff.obj",mimeType = "application/octet-stream")]
		private var puff_obj_data:Class;
		// The terrain mesh data - 8192 polygons
		[Embed (source = "art/terrain.obj",mimeType = "application/octet-stream")]
		private var terrain_obj_data:Class;
		// an asteroid field - 3280 polygons
		[Embed (source = "art/asteroids.obj",mimeType = "application/octet-stream")]
		private var asteroids_obj_data:Class;
		// the sky - 768 polygons
		[Embed (source = "art/sky.obj",mimeType = "application/octet-stream")]
		private var sky_obj_data:Class;
		// explosion start - 336 polygons
		[Embed (source = "art/explosion1.obj",mimeType = "application/octet-stream")]
		private var explosion1_data:Class;
		// explosion end - 336 polygons
		[Embed (source = "art/explosion2.obj",mimeType = "application/octet-stream")]
		private var explosion2_data:Class;
		// bullet - 8 polygons
		[Embed (source = "art/bullet.obj",mimeType = "application/octet-stream")]
		private var bullet_data:Class;
		
		// sentry guns - all share one texture
		[Embed (source = "art/sentrygun01.obj",mimeType = "application/octet-stream")]
		private var sentrygun01_data:Class;
		[Embed (source = "art/sentrygun02.obj",mimeType = "application/octet-stream")]
		private var sentrygun02_data:Class;
		[Embed (source = "art/sentrygun03.obj",mimeType = "application/octet-stream")]
		private var sentrygun03_data:Class;
		[Embed (source = "art/sentrygun04.obj",mimeType = "application/octet-stream")]
		private var sentrygun04_data:Class;
		// fuel tanks - uses same texture as sentries
		[Embed (source = "art/fueltank01.obj",mimeType = "application/octet-stream")]
		private var fueltank01_data:Class;
		[Embed (source = "art/fueltank02.obj",mimeType = "application/octet-stream")]
		private var fueltank02_data:Class;
		
		// large boss ship
		[Embed (source = "art/boss.obj",mimeType = "application/octet-stream")]
		private var boss_data:Class;
		// asteroid with a tunnel you can fly THROUGH
		[Embed (source = "art/astro_tunnel.obj",mimeType = "application/octet-stream")]
		private var astro_tunnel_data:Class;
		
		// a single asteroid
		[Embed (source = "art/asteroid_00.obj",mimeType = "application/octet-stream")]
		private var asteroid_00_data:Class;
		[Embed (source = "art/asteroid_01.obj",mimeType = "application/octet-stream")]
		private var asteroid_01_data:Class;
		[Embed (source = "art/asteroid_02.obj",mimeType = "application/octet-stream")]
		private var asteroid_02_data:Class;
		
		// a large flat "mesa" (circular rock with a flat top)
		[Embed (source = "art/island.obj",mimeType = "application/octet-stream")]
		private var island_data:Class;
		// space stations and "slabs of tech"
		[Embed (source = "art/slab01.obj",mimeType = "application/octet-stream")]
		private var slab01_data:Class;
		[Embed (source = "art/slab02.obj",mimeType = "application/octet-stream")]
		private var slab02_data:Class;
		[Embed (source = "art/slab03.obj",mimeType = "application/octet-stream")]
		private var slab03_data:Class;
		[Embed (source = "art/slab04.obj",mimeType = "application/octet-stream")]
		private var slab04_data:Class;
		[Embed (source = "art/station01.obj",mimeType = "application/octet-stream")]
		private var station01_data:Class;
		[Embed (source = "art/station02.obj",mimeType = "application/octet-stream")]
		private var station02_data:Class;
		[Embed (source = "art/station03.obj",mimeType = "application/octet-stream")]
		private var station03_data:Class;
		[Embed (source = "art/station04.obj",mimeType = "application/octet-stream")]
		private var station04_data:Class;
		[Embed (source = "art/station05.obj",mimeType = "application/octet-stream")]
		private var station05_data:Class;
		[Embed (source = "art/station06.obj",mimeType = "application/octet-stream")]
		private var station06_data:Class;
		[Embed (source = "art/station07.obj",mimeType = "application/octet-stream")]
		private var station07_data:Class;
		[Embed (source = "art/station08.obj",mimeType = "application/octet-stream")]
		private var station08_data:Class;
		[Embed (source = "art/station09.obj",mimeType = "application/octet-stream")]
		private var station09_data:Class;
		[Embed (source = "art/station10.obj",mimeType = "application/octet-stream")]
		private var station10_data:Class;
		// a spinning wormhole
		[Embed (source = "art/wormhole.obj",mimeType = "application/octet-stream")]
		private var wormhole_data:Class;
		
		// the tiny image that defines which RGB
		// in the map image represents which mesh
		[Embed(source = 'art/level_key.gif')]
		private static const LEVELKEYDATA:Class;
		private var level_key:Bitmap = new LEVELKEYDATA();
		// the image that represents the game level
		[Embed(source = 'art/level_00.gif')]
		private static const LEVELDATA:Class;
		private var level_data:Bitmap = new LEVELDATA();
		
		
		public function Chapter9Demp(stage:Stage)
		{
			super(stage);
			
			// start the sound immediately
			intro_sound.play();
			// preloaded classes are not immediately added to stage
			init();
			
		}
		private function init(e:Event = null):void
		{
			// start the game timer
			gametimer = new Game_timer(heartbeat,3333);
			gameinput = new utils.newclass.Game_input(stage, gamestart);
			// create some empty arrays
			props = new Vector.<Molehill_entity>();
			
			// add some text labels
			initGUI();
			// and request a context3D from Molehill
			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
			stage.stage3Ds[0].x = 0;
			stage.stage3Ds[0].y = 0;
			
		}
		
		private function initGUI():void
		{
			// titlescreen
			addChild(title_screen);
			// add a title screen "game over" notice
			var gameoverFormat:TextFormat = new TextFormat();
			gameoverFormat.color = 0x000000;
			gameoverFormat.size = 32;
			gameoverFormat.font = "gui_font";
			gameoverFormat.align = TextFormatAlign.CENTER;
			titlescreen_tf = new TextField();
			titlescreen_tf.embedFonts = true;
			titlescreen_tf.x = 0;
			titlescreen_tf.y = 180;
			titlescreen_tf.width = 640;
			titlescreen_tf.height = 300;
			titlescreen_tf.autoSize = TextFieldAutoSize.NONE;
			titlescreen_tf.selectable = false;
			titlescreen_tf.defaultTextFormat = gameoverFormat;
			titlescreen_tf.filters = [
				new GlowFilter(0xFF0000, 1, 2, 2, 2, 2),
				new GlowFilter(0xFFFFFF, 1, 16, 16, 2, 2)];
			titlescreen_tf.text = "CLICK TO PLAY\n\n"
				+ "Your objective: get to the\n"
				+ "end of the Kessel Run alive.\n"
				+ "Get hit 3x and you will be destroyed!";
			addChild(titlescreen_tf);
			// heads-up-display overlay
			addChild(hud_overlay);
			// a text format descriptor used by all gui labels
			var myFormat:TextFormat = new TextFormat();
			myFormat.color = 0xFFFFAA;
			myFormat.size = 16;
			myFormat.font = "gui_font";
			// create an FPSCounter that displays the framerate on screen
			fps_tf = new TextField();
			fps_tf.embedFonts = true;
			fps_tf.x = 0;
			fps_tf.y = 0;
			fps_tf.selectable = false;
			fps_tf.autoSize = TextFieldAutoSize.LEFT;
			fps_tf.defaultTextFormat = myFormat;
			fps_tf.filters = [new GlowFilter(0x000000, 1, 2, 2, 2, 2)];
			fps_tf.text = "Initializing Molehill...";
			addChild(fps_tf);
			// create a score display
			score_tf = new TextField();
			score_tf.embedFonts = true;
			score_tf.x = 540;
			score_tf.y = 0;
			score_tf.selectable = false;
			score_tf.autoSize = TextFieldAutoSize.LEFT;
			score_tf.defaultTextFormat = myFormat;
			score_tf.filters = [new GlowFilter(0xFF0000, 1, 2, 2, 2, 2)];
			addChild(score_tf);
			// add a health meter
			health_tf = new TextField();
			health_tf.embedFonts = true;
			health_tf.x = 232;
			health_tf.y = 3;
			health_tf.selectable = false;
			health_tf.autoSize = TextFieldAutoSize.LEFT;
			health_tf.defaultTextFormat = myFormat;
			health_tf.filters = [new GlowFilter(0xFFFFFF, 1, 4, 4, 4, 2)];
			health_tf.text = health_text_3;
			addChild(health_tf);
		}
		
		public function uploadTextureWithMipmaps(dest:Texture, src:BitmapData):void
		{
			var ws:int = src.width;
			var hs:int = src.height;
			var level:int = 0;
			var tmp:BitmapData;
			var transform:Matrix = new Matrix();
			tmp = new BitmapData( src.width, src.height, true, 0x00000000);
			while ( ws >= 1 && hs >= 1 )
			{
				tmp.draw(src, transform, null, null, null, true);
				dest.uploadFromBitmapData(tmp, level);
				transform.scale(0.5, 0.5);
				level++;
				ws >>= 1;
				hs >>= 1;
				if (hs && ws)
				{
					tmp.dispose();
					tmp = new BitmapData(ws, hs, true, 0x00000000);
				}
			}
			tmp.dispose();
		}
		
		private function init_texture(bmp:Bitmap):Texture
		{
			var tex:Texture;
			tex = context3D.createTexture(bmp.width, bmp.height,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(tex, bmp.bitmapData);
			return tex;
		}
		
		private function onContext3DCreate(event:Event):void
		{
			// Remove existing frame handler. Note that a context
			// loss can occur at any time which will force you
			// to recreate all objects we create here.
			// A context loss occurs for instance if you hit
			// CTRL-ALT-DELETE on Windows.
			// It takes a while before a new context is available
			// hence removing the enterFrame handler is important!
			// Obtain the current context
			var t:Stage3D = event.target as Stage3D;
			context3D = t.context3D;
			if (context3D == null)
			{
				// Currently no 3d context is available (error!)
				trace('ERROR: no context3D - video driver problem?');
				return;
			}
			trace(context3D.driverInfo);
			// this code will work for a future version of Molehill
			// after public release of Flash 11 (see the init() function)
			/*
			context3D.width = stage.width;
			context3D.height = stage.height;
			*/
			// Disabling error checking will drastically improve performance.
			// If set to true, Flash sends helpful error messages regarding
			// AGAL compilation errors, uninitialized program constants, etc.
			context3D.enableErrorChecking = false;
			// The 3d back buffer size is in pixels (2=antialiased)
			context3D.configureBackBuffer(stage.width, stage.height, 2, true);
			// assemble all the shaders we need
			initShaders();
			// init all textures
			playerTexture = init_texture(playerTextureData);
			terrainTexture = init_texture(terrainTextureData);
			cratersTexture = init_texture(cratersTextureData);
			puffTexture = init_texture(puffTextureData);
			skyTexture = init_texture(skyTextureData);
			particle1texture = init_texture(particle1bitmap);
			particle2texture = init_texture(particle2bitmap);
			particle3texture = init_texture(particle3bitmap);
			particle4texture = init_texture(particle4bitmap);
			sentryguntexture = init_texture(sentrygunTextureBitmap);
			bosstexture = init_texture(bossTextureBitmap);
			techtexture = init_texture(techTextureBitmap);
			// Initialize our mesh data - requires shaders and textures first
			initData();
			// create projection matrix for our 3D scene
			projectionmatrix.identity();
			// 45 degrees FOV, 640/480 aspect ratio, 0.1=near, 150000=far
			projectionmatrix.perspectiveFieldOfViewRH(
				45, stage.width / stage.height, 0.01, 150000.0);
			// start the render loop!
			addEventListener(Event.ENTER_FRAME,enterFrame);
			
			
		}
		
		private function initShaders():void
		{
			// A simple vertex shader which does a 3D transformation
			// for simplicity, it is used by all four shaders
			var vertexShaderAssembler:AGALMiniAssembler =
				new AGALMiniAssembler();
			vertexShaderAssembler.assemble
				(
					Context3DProgramType.VERTEX,
					// 4x4 matrix multiply to get camera angle
					"m44 op, va0, vc0\n" +
					// tell fragment shader about XYZ
					"mov v0, va0\n" +
					// tell fragment shader about UV
					"mov v1, va1\n" +
					// tell fragment shader about RGBA
					"mov v2, va2"
				);
			// textured using UV coordinates
			var fragmentShaderAssembler1:AGALMiniAssembler  = new AGALMiniAssembler();
			fragmentShaderAssembler1.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the texture color from texture 0
					// and uv coordinates from varying register 1
					// and store the interpolated value in ft0
					"tex ft0, v1, fs0 <2d,linear,repeat,miplinear>\n"+
					// move this value to the output color
					"mov oc, ft0\n"
				);
			// combine shaders into a program which we then upload to the GPU
			shaderProgram1 = context3D.createProgram();
			shaderProgram1.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler1.agalcode);
		}
		
		private function initData():void
		{
			// create a particle system
			particle_system = new Game_particlesystem;
			enemies = new Game_actorpool;
			player_bullets = new Game_actorpool;
			enemy_bullets = new Game_actorpool;
			gamelevel = new Game_actorpool;
			// create the camera entity
			trace("Creating the camera entity...");
			chase_camera = new Molehill_entity();
			trace("Parsing the sky...");
			sky = new Molehill_entity(sky_obj_data,context3D,shaderProgram1,skyTexture);
			sky.depth_test = false;
			sky.depth_test_mode = Context3DCompareMode.LESS;
			sky.culling_mode = Context3DTriangleFace.NONE;
			sky.depth_draw = false;
			sky.z = -10000.0;
			sky.scaleX = 20;
			sky.scaleY = 10;
			sky.scaleZ = 20;
			sky.rotationDegreesY = -90;
			sky.rotationDegreesX = -90;
			props.push(sky);
			// create the player model
			trace("Creating the player entity...");
			player = new Game_actor(my_obj_data5,context3D,shaderProgram1,playerTexture);
			// start far back
			player.z = 0;
			player.particles = particle_system;
			// collision detection
			player.collides = true;
			player.radius = 1;
			player.name = "player1";
			player.classname = "player";
			player.health = 3;
			// create enemies
			actor = new Game_actor(my_obj_data5,context3D,shaderProgram1,playerTexture,4);
			actor.collides = true;
			actor.radius = 2;
			actor.particles = particle_system;
			actor.bullets = enemy_bullets;
			actor.shoot_name = 'evilbolt';
			actor.shoot_delay = 1000;
			actor.shoot_random_delay = 1500;
			actor.shoot_dist = 100;
			actor.shoot_velocity = 35; // bullets move slowish
			actor.shoot_at = player;
			actor.pos_velocity = new Vector3D(0, 0, 20); // fly towards us
			enemies.define_actor("enemy_cruiser", actor);
			actor = new Game_actor(boss_data,context3D,shaderProgram1,bosstexture, 32); // scaled up
			actor.collides = true;
			actor.radius = 16;
			actor.pos_velocity = new Vector3D(0, 0, -5); // stay with us
			actor.particles = particle_system;
			actor.bullets = enemy_bullets;
			actor.shoot_name = 'evilbolt';
			actor.shoot_delay = 500;
			actor.shoot_random_delay = 500;
			actor.shoot_dist = 200;
			actor.shoot_velocity = 30; // bullets move slowish
			actor.shoot_at = player;
			actor.points = 1111;
			actor.health = 20; // #hits to destroy
			enemies.define_actor("enemy_boss", actor);
			// create bullets
			actor = new Game_actor(bullet_data,context3D,shaderProgram1,particle1texture);
			actor.collides = true;
			actor.radius = 1;
			actor.age_max = 5000;
			actor.particles = particle_system;
			actor.spawn_when_no_health = "sparks";
			actor.spawn_when_max_age = "sparks";
			actor.spawn_when_created = "sparks";
			actor.blend_src = Context3DBlendFactor.ONE;
			actor.blend_dst = Context3DBlendFactor.ONE;
			actor.culling_mode = Context3DTriangleFace.NONE;
			actor.depth_test_mode = Context3DCompareMode.LESS;
			actor.depth_draw = false;
			actor.depth_test = false;
			actor.pos_velocity = new Vector3D(0, 0, -50);
			actor.run_when_max_age = reset_combo;
			player_bullets.define_actor("bolt", actor);
			actor = new Game_actor(bullet_data,context3D,shaderProgram1,particle2texture);
			actor.collides = true;
			actor.radius = 1;
			actor.age_max = 5000;
			actor.particles = particle_system;
			actor.spawn_when_no_health = "sparks";
			actor.spawn_when_max_age = "sparks";
			actor.spawn_when_created = "sparks";
			actor.blend_src = Context3DBlendFactor.ONE;
			actor.blend_dst = Context3DBlendFactor.ONE;
			actor.culling_mode = Context3DTriangleFace.NONE;
			actor.depth_test_mode = Context3DCompareMode.LESS;
			actor.depth_draw = false;
			actor.depth_test = false;
			actor.pos_velocity = new Vector3D(0, 0, 25);
			enemy_bullets.define_actor("evilbolt", actor);
			// add some terrain to the props list
			trace("Parsing the terrain...");
			var terrain:Molehill_entity =new Molehill_entity(terrain_obj_data,context3D,shaderProgram1,terrainTexture, 0.5);
			terrain.rotationDegreesZ = 90;
			terrain.y = -50;
			props.push(terrain);
			// add an asteroid field to the props list
			trace("Parsing the asteroid field...");
			asteroids1 = new Molehill_entity(asteroids_obj_data,context3D,shaderProgram1,cratersTexture);
			asteroids1.rotationDegreesZ = 90;
			asteroids1.scaleXYZ = 200;
			asteroids1.y = 500;
			asteroids1.z = -1100;
			props.push(asteroids1);
			trace("Cloning the asteroid field...");
			// use the same mesh in multiple locations
			asteroids2 = asteroids1.clone();
			asteroids2.z = -1500;
			props.push(asteroids2);
			asteroids3 = asteroids1.clone();
			asteroids3.z = -1900;
			props.push(asteroids3);
			asteroids4 = asteroids1.clone();
			asteroids4.z = -1900;
			asteroids4.y = -500;
			props.push(asteroids4);
			trace("Parsing the asteroid tunnel...");
			actor = new Game_actor(astro_tunnel_data,context3D,shaderProgram1,cratersTexture);
			actor.rot_velocity = new Vector3D(0, 0, 30);
			actor.collides = false;
			actor.radius = 620;
			enemies.define_actor("astro_tunnel", actor);
			actor = new Game_actor(asteroid_00_data,context3D,shaderProgram1,cratersTexture);
			actor.rot_velocity = new Vector3D(0, 30, 5);
			actor.collides = true;
			actor.radius = 13;
			enemies.define_actor("asteroid_00", actor);
			actor = new Game_actor(asteroid_01_data,context3D,shaderProgram1,cratersTexture);
			actor.rot_velocity = new Vector3D(0, -30, -5);
			actor.collides = true;
			actor.radius = 14;
			enemies.define_actor("asteroid_01", actor);
			actor = new Game_actor(asteroid_02_data,context3D,shaderProgram1,cratersTexture);
			actor.rot_velocity = new Vector3D(0, 20, 0);
			actor.collides = true;
			actor.radius = 14;
			enemies.define_actor("asteroid_02", actor);
			actor = new Game_actor(island_data,context3D,shaderProgram1,cratersTexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("island", actor);
			actor = new Game_actor(slab01_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("slab01", actor);
			actor = new Game_actor(slab02_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("slab02", actor);
			actor = new Game_actor(slab03_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("slab03", actor);
			actor = new Game_actor(slab04_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("slab04", actor);
			actor = new Game_actor(station01_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station01", actor);
			actor = new Game_actor(station02_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station02", actor);
			actor = new Game_actor(station03_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station03", actor);
			actor = new Game_actor(station04_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station04", actor);
			actor = new Game_actor(station05_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station05", actor);
			actor = new Game_actor(station06_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station06", actor);
			actor = new Game_actor(station07_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station07", actor);
			actor = new Game_actor(station08_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station08", actor);
			actor = new Game_actor(station09_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station09", actor);
			actor = new Game_actor(station10_data,context3D,shaderProgram1,techtexture,1,false);
			actor.collides = false;
			actor.radius = 64;
			enemies.define_actor("station10", actor);
			actor = new Game_actor(wormhole_data,context3D,shaderProgram1,particle1texture,0.1);
			actor.collides = false;
			actor.radius = 128;
			actor.blend_src = Context3DBlendFactor.ONE;
			actor.blend_dst = Context3DBlendFactor.ONE;
			actor.culling_mode = Context3DTriangleFace.NONE;
			actor.depth_test_mode = Context3DCompareMode.LESS;
			actor.depth_draw = false;
			actor.depth_test = false;
			actor.rot_velocity = new Vector3D(0, 0, -120);
			enemies.define_actor("wormhole", actor);
			// new enemies
			actor = new Game_actor(sentrygun01_data,context3D,shaderProgram1,sentryguntexture,4);
			actor.collides = true;
			actor.radius = 2;
			actor.particles = particle_system;
			actor.bullets = enemy_bullets;
			actor.shoot_name = 'evilbolt';
			actor.shoot_delay = 1500;
			actor.shoot_random_delay = 500;
			actor.shoot_dist = 100;
			actor.shoot_velocity = 25;
			actor.shoot_at = player;
			enemies.define_actor("sentrygun01", actor);
			actor = new Game_actor(sentrygun02_data,context3D,shaderProgram1,sentryguntexture,4);
			actor.collides = true;
			actor.radius = 2;
			actor.particles = particle_system;
			actor.bullets = enemy_bullets;
			actor.shoot_name = 'evilbolt';
			actor.shoot_delay = 1500;
			actor.shoot_random_delay = 500;
			actor.shoot_dist = 100;
			actor.shoot_velocity = 25;
			actor.shoot_at = player;
			enemies.define_actor("sentrygun02", actor);
			actor = new Game_actor(sentrygun03_data,context3D,shaderProgram1,sentryguntexture,4);
			actor.collides = true;
			actor.radius = 2;
			actor.particles = particle_system;
			actor.bullets = enemy_bullets;
			actor.shoot_name = 'evilbolt';
			actor.shoot_delay = 1500;
			actor.shoot_random_delay = 500;
			actor.shoot_dist = 100;
			actor.shoot_velocity = 25;
			actor.shoot_at = player;
			enemies.define_actor("sentrygun03", actor);
			actor = new Game_actor(sentrygun04_data,context3D,shaderProgram1,sentryguntexture,4);
			actor.collides = true;
			actor.radius = 2;
			actor.particles = particle_system;
			actor.bullets = enemy_bullets;
			actor.shoot_name = 'evilbolt';
			actor.shoot_delay = 1500;
			actor.shoot_random_delay = 500;
			actor.shoot_dist = 100;
			actor.shoot_velocity = 25;
			actor.shoot_at = player;
			enemies.define_actor("sentrygun04", actor);
			actor = new Game_actor(fueltank01_data,context3D,shaderProgram1,sentryguntexture,4);
			actor.collides = true;
			actor.radius = 2;
			enemies.define_actor("fueltank01", actor);
			actor = new Game_actor(fueltank02_data,context3D,shaderProgram1,sentryguntexture,4);
			actor.collides = true;
			actor.radius = 2;
			enemies.define_actor("fueltank02", actor);
			trace("Parsing the engine glow...");
			engine_glow = new Molehill_entity(puff_obj_data,context3D,shaderProgram1,puffTexture);
			// follow the player's ship
			engine_glow.follow(player);
			// draw as a transparent particle
			engine_glow.blend_src = Context3DBlendFactor.ONE;
			engine_glow.blend_dst = Context3DBlendFactor.ONE;
			engine_glow.depth_test = true;
			engine_glow.depth_test_mode = Context3DCompareMode.ALWAYS;
			engine_glow.culling_mode = Context3DTriangleFace.NONE;
			engine_glow.y = -0.6;
			engine_glow.scaleXYZ = 0.00001;
			props.push(engine_glow); // a prop, not a particle
			trace("Creating an explosion particle system...");
			// define the types of particles
			particle_system.define_particle("explosion",new Molehill_particle(explosion1_data, context3D,puffTexture, explosion2_data));
			particle_system.define_particle("bluebolt",new Molehill_particle(explosion1_data, context3D,particle1texture, explosion2_data));
			particle_system.define_particle("greenpuff",new Molehill_particle(explosion1_data, context3D,particle2texture, explosion2_data));
			particle_system.define_particle("ringfire",new Molehill_particle(explosion1_data, context3D,particle3texture, explosion2_data));
			particle_system.define_particle("sparks",new Molehill_particle(explosion1_data, context3D,particle4texture, explosion2_data));
			
			// create a level using
			// our map images as the blueprint
			trace("Spawning the game level parser...");
			var level:Game_level_parser = new Game_level_parser();
			var enemy_names:Array =	["enemy_cruiser"];
			// spiral enemy formation - works great
			level.spawn_actors(level_key.bitmapData,level_data.bitmapData,enemy_names,enemies,
				-8, 0, player.z - level_data.height * 8,
				2, 8, 0, 8);
			var rock_names:Array =
				[null,
					"asteroid_00",
					"asteroid_01",
					"asteroid_02",
					"sentrygun01",
					"sentrygun02",
					"sentrygun03",
					"sentrygun04",
					"fueltank01",
					"fueltank02",
					"enemy_boss",
					"island",
					"slab01",
					"slab02",
					"slab03",
					"slab04",
					"station01",
					"station02",
					"station03",
					"station04",
					"station05",
					"station06",
					"station07",
					"station08",
					"station09",
					"station10",
					"wormhole"
					//,"astro_tunnel"
				];
			// flat enemy formation
			var game_level_length:Number;
			game_level_length = level.spawn_actors(
				level_key.bitmapData,
				level_data.bitmapData,
				rock_names,
				enemies,
				-8, 0, player.z - level_data.height * 8,
				2, 8, 0, 0);
			// now that we know how long the map is
			// remember what z coord is the "finish line"
			player_minz = player.z - game_level_length;
		}
		
		private function render_scene():void
		{
			scene_polycount = 0;
			viewmatrix.identity();
			// look at the player
			viewmatrix.append(chase_camera.transform);
			viewmatrix.invert();
			// tilt down a little
			viewmatrix.appendRotation(15, Vector3D.X_AXIS);
			// if mouselook is on:
			viewmatrix.appendRotation(
				gameinput.camera_angle_x, Vector3D.X_AXIS);
			viewmatrix.appendRotation(
				gameinput.camera_angle_y, Vector3D.Y_AXIS);
			viewmatrix.appendRotation(
				gameinput.camera_angle_z, Vector3D.Z_AXIS);
			// render the player mesh from the current camera angle
			player.render(viewmatrix, projectionmatrix);
			scene_polycount += player.polycount;
			// loop through all known entities and render them
			for each (entity in props)
			{
				entity.render(viewmatrix, projectionmatrix);
				scene_polycount += entity.polycount;
			}
			// hide enemies that are far away OR behind us
			// these distances are scaled by the entity's radius
			// so that big meshes are visible from
			// farther away to prevent "pop-in"
			enemies.hide_distant(player.position, 150, 2);
			player_bullets.hide_distant(player.position, 150, 2);
			enemy_bullets.hide_distant(player.position, 150, 2);
			enemies.render(viewmatrix, projectionmatrix);
			scene_polycount += enemies.totalpolycount;
			player_bullets.render(viewmatrix, projectionmatrix);
			scene_polycount += player_bullets.totalpolycount;
			enemy_bullets.render(viewmatrix, projectionmatrix);
			scene_polycount += enemy_bullets.totalpolycount;
			particle_system.render(viewmatrix, projectionmatrix);
			scene_polycount += particle_system.totalpolycount;
		}
		
		private function gamestart():void
		{
			if (game_state == state_titlescreen)
			{
				trace('GAME START!');
				game_state = state_playing;
				score = 0;
				combo = 0;
				combo_best = 0;
				player.health = 3;
				health_tf.text = health_text_3;
				player.x = 0;
				player.y = 0;
				player.z = 0;
				player.active = true;
				player.visible = true;
				player.collides = true;
				next_shoot_time = 0;
				invulnerability_time_left = 0;
				player.spawn_constantly = '';
				player.spawn_constantly_delay = 0;
				player.updateTransformFromValues();
				if (contains(title_screen))
					removeChild(title_screen);
				if (contains(titlescreen_tf))
					removeChild(titlescreen_tf);
				gametimer.game_elapsed_time = 0;
				gametimer.game_start_time =
					gametimer.last_frame_time =
					gametimer.current_frame_time =
					getTimer();
			}
		}
		
		private function gameover(beat_the_game:Boolean = false):void
		{
			if (game_state != state_titlescreen)
			{
				trace('GAME OVER!');
				game_state = state_titlescreen;
				addChild(title_screen);
				if (beat_the_game)
				{
					titlescreen_tf.text = "VICTORY!\n"
						+ "You beat the Kessel Run in " + Math.round(
							gametimer.game_elapsed_time / 1000) + " seconds\n"
						+ "Your score: " + score + "\n"
						+ "Highest combo: " + combo_best + "x\n";
				}
				else // player was destroyed
				{
					titlescreen_tf.text = "GAME OVER\n"
						+ "You were destroyed after " + Math.round(
							gametimer.game_elapsed_time / 1000) + " seconds\n"
						+ "Your score: " + score + "\n"
						+ "Highest combo: " + combo_best + "x\n";
				}
				// turn off the engines
				engine_glow.scaleXYZ = 0.00001;
				// make the gameover text visible
				addChild(titlescreen_tf);
			}
		}
		
		private var combocomment:String = '';
		private function update_score():void
		{
			if (combo < 10) combocomment = combo + "x combo";
			else if (combo < 20) combocomment =
				combo + "x combo\nNot bad!";
			else if (combo < 30) combocomment =
				combo + "x combo\nGreat!";
			else if (combo < 40) combocomment =
				combo + "x combo\nSkilled!";
			else if (combo < 50) combocomment =
				combo + "x combo\nIncredible!";
			else if (combo < 60) combocomment =
				combo + "x combo\nAmazing!";
			else if (combo < 70) combocomment =
				combo + "x combo\nUnbelievable!";
			else if (combo < 80) combocomment =
				combo + "x combo\nInsane!";
			else if (combo < 90) combocomment =
				combo + "x combo\nFantastic!";
			else if (combo < 100) combocomment =
				combo + "x combo\nHeroic!";
			else combocomment = combo + "x combo\nLegendary!";
			if (combo > combo_best) combo_best = combo;
			// padded with zeroes
			if (score < 10) score_tf.text =
				'Score: 00000' + score + "\n" + combocomment;
			else if (score < 100) score_tf.text =
				'Score: 0000' + score + "\n" + combocomment;
			else if (score < 1000) score_tf.text =
				'Score: 000' + score + "\n" + combocomment;
			else if (score < 10000) score_tf.text =
				'Score: 00' + score + "\n" + combocomment;
			else if (score < 100000) score_tf.text =
				'Score: 0' + score + "\n" + combocomment;
			else score_tf.text = 'Score: ' + score + "\n" + combocomment;
		}
		
		private function reset_combo():void
		{
			trace("Player bullet missed: resetting combo.");
			combo = 0;
		}
		
		private function hit_an_enemy(
			culprit:Game_actor, victim:Game_actor):void
		{
			trace("hit_an_enemy!");
			blast_sound.play();
			victim.health--;
			if (victim.health <= 0)
			{
				particle_system.spawn("explosion", victim.transform);
				score += victim.points;
				victim.die();
			}
			particle_system.spawn("explosion", culprit.transform); // ricochet
			culprit.die();
			combo++;
		}
		private function player_got_hit(
			culprit:Game_actor, victim:Game_actor):void
		{
			trace("Player got hit!");
			particle_system.spawn("explosion", victim.transform, 4444, 0, 5);
			particle_system.spawn("explosion", culprit.transform, 3333, 0, 3);
			culprit.die();
			blast_sound.play();
			player.health--;
			trace("Player health = " + player.health);
			invulnerability_time_left =
				invulnerability_ms_when_hit;
			if (player.health == 3)
			{
				health_tf.text = health_text_3;
			}
			if (player.health == 2)
			{
				health_tf.text = health_text_2;
			}
			if (player.health == 1)
			{
				trace("Player is almost dead!");
				player.spawn_constantly = 'explosion';
				player.spawn_constantly_delay = 100;
				player.spawn_constantly_next = 0;
				health_tf.text = health_text_1;
			}
			if (player.health <= 0)
			{
				trace("Player's health is zero!");
				health_tf.text = health_text_0;
				gameover();
			}
		}
		
		private function handle_player_input(frame_ms:uint):void
		{
			// handle player input
			var move_amount:Number = move_speed * frame_ms;
			// in our game we are always flying forward
			// while moving up/down/left/right to dodge
			// EXAMPLE: a simple way to slide around - it works,
			// but assumes you are always facing down the z axis
			/*
			if (gameinput.pressing.up) player.y += move_amount;
			if (gameinput.pressing.down) player.y -= move_amount;
			if (gameinput.pressing.left) player.x -= move_amount;
			if (gameinput.pressing.right) player.x += move_amount;
			*/
			// this method does the same thing but would also
			// work if we were facing other directions
			// because the motion is relative to our angles
			if (gameinput.pressing.up)
				player.move_up(move_amount);
			if (gameinput.pressing.down)
				player.move_down(move_amount);
			if (gameinput.pressing.left)
				player.move_left(move_amount);
			if (gameinput.pressing.right)
				player.move_right(move_amount);
			// EXAMPLE: turning the ship via euler angles
			// will break due to gimbal lock at 180..-180
			// do not rotate things using this technique:
			/*
			if (gameinput.pressing.left)
			player.rotationDegreesY += move_amount*4;
			if (gameinput.pressing.right)
			player.rotationDegreesY -= move_amount*4;
			if (gameinput.pressing.up)
			player.rotationDegreesX -= move_amount*4;
			if (gameinput.pressing.down)
			player.rotationDegreesX += move_amount*4;
			*/
			/*
			// EXAMPLE: FPS style 6DOF rotation
			// avoids gimbal lock by using quaternion rotation
			// which is a way of rotating using a single axis
			if (gameinput.pressing.left)
			player.transform.prependRotation(move_amount * 4,
			Vector3D.Y_AXIS);// player.upvector);
			if (gameinput.pressing.right)
			player.transform.prependRotation(-move_amount * 4,
			Vector3D.Y_AXIS);//player.upvector);
			if (gameinput.pressing.up)
			player.transform.prependRotation(-move_amount * 4,
			Vector3D.X_AXIS);//player.rightvector);
			if (gameinput.pressing.down)
			player.transform.prependRotation(move_amount * 4,
			Vector3D.X_AXIS);//player.rightvector);
			*/
			/*
			// EXAMPLE: slide forward and backward
			// good for walking such as in an FPS game
			if (gameinput.pressing.up)
			player.move_forward(move_amount);
			if (gameinput.pressing.down)
			player.move_backward(move_amount);
			// "strafe" sliding side to side - good for walking
			if (gameinput.pressing.strafe_left)
			player.move_left(move_amount);
			if (gameinput.pressing.strafe_right)
			player.move_right(move_amount);
			*/
			// keep moving forward: in our flying game
			// we constantly move and use arrow keys to dodge
			player.move_forward(fly_speed);
			// force the player to stay within bounds
			if ((player_maxx != 0) && (player.x > player_maxx))
				player.x = player_maxx;
			if ((player_maxy != 0) && (player.y > player_maxy))
				player.y = player_maxy;
			if ((player_maxz != 0) && (player.z > player_maxz))
				player.z = player_maxz;
			if ((player_minx != 0) && (player.x < player_minx))
				player.x = player_minx;
			if ((player_miny != 0) && (player.y < player_miny))
				player.y = player_miny;
			if ((player_minz != 0) && (player.z < player_minz))
			{
				player.z = player_minz;
				if (level_complete_player_minz)
				{
					// we got to the "finish line"
					trace('LEVEL COMPLETE!');
					gameover(true)
				}
			}
			if (gameinput.pressing.fire)
			{
				if (gametimer.game_elapsed_time >= next_shoot_time)
				{
					//trace("Fire!");
					next_shoot_time =
						gametimer.game_elapsed_time + shoot_delay;
					// shoot a bullet
					actor = player_bullets.spawn("bolt", player.transform);
					actor.updateValuesFromTransform();
					actor.rotationDegreesY = 90;
					// we don't want to be able to shoot ourselves
					actor.owner = player;
					gun_sound.play();
				}
			}
		}
		
		private var profiling_start:uint = 0;
		private var profiling_end:uint = 0;
		
		private function game_step(frame_ms:uint):void
		{
			// time how long this function takes
			profiling_start = getTimer();
			// if we are at the title screen, don't move the player
			if (game_state == state_playing)
				handle_player_input(frame_ms);
			// follow behind the player
			// no matter what direction it is facing
			chase_camera.x = player.x;
			chase_camera.y = player.y;
			chase_camera.z = player.z;
			chase_camera.rotationDegreesY = player.rotationDegreesY;
			chase_camera.rotationDegreesX = chase_camera_xangle;
			chase_camera.move_backward(chase_camera_distance);
			chase_camera.move_up(chase_camera_height);
			// during the title screen, add some camera wobble
			if (game_state != state_playing)
			{
				var wobble_size:Number = 4;
				var wobble_ms:Number = 2000;
				chase_camera.move_up(
					Math.cos(gametimer.game_elapsed_time / wobble_ms)
					/ Math.PI * wobble_size);
				chase_camera.move_left(
					Math.sin(gametimer.game_elapsed_time / wobble_ms)
					/ Math.PI * wobble_size);
				chase_camera.move_forward(
					Math.sin(gametimer.game_elapsed_time / wobble_ms / 2)
					/ Math.PI * wobble_size * 2);
			}
			// animate the asteroids in the background
			asteroids1.rotationDegreesX += asteroid_rotation_speed * frame_ms;
			asteroids2.rotationDegreesX -= asteroid_rotation_speed * frame_ms;
			asteroids3.rotationDegreesX += asteroid_rotation_speed * frame_ms;
			asteroids4.rotationDegreesX -= asteroid_rotation_speed * frame_ms;
			// advance all particles based on time
			particle_system.step(frame_ms);
			// don't process any more if we are at the title screen
			if (game_state != state_playing) return;
			// animate the engine glow - spin fast and pulsate slowly
			engine_glow.rotationDegreesZ += 10 * frame_ms;
			engine_glow.scaleXYZ =
				Math.cos(gametimer.game_elapsed_time / 66) / 20 + 0.75;
			player_bullets.step(frame_ms, enemies.colliding, hit_an_enemy);
			// when the player gets damaged, they become
			// invulnerable for a short perod of time
			if (invulnerability_time_left > 0)
			{
				invulnerability_time_left -= frame_ms;
				if (invulnerability_time_left <= 0)
				{
					trace("Invulnerability wore off.");
				}
			}
			// check for collisions with the player
			// between enemy ships and player ship
			// unless we are invulnerable
			if (invulnerability_time_left <= 0)
			{
				// step enemy bullets and check for collisions
				enemy_bullets.step(frame_ms, player.colliding, player_got_hit);
				// step enemies and check to see if we collided with one
				enemies.step(frame_ms, player.colliding, player_got_hit);
			}
			else
			{
				// player is invulnerable:
				// step bullets but don't check collisions
				enemy_bullets.step(frame_ms);
				// setp enemies but don't check collisions
				enemies.step(frame_ms);
			}
			// allow the player to update things like particles
			player.step(frame_ms);
			// time how long this function takes
			profiling_end = getTimer();
		}
		
		// for efficiency, this function only runs occasionally
		// ideal for calculations that don't need to be run every frame
		private function heartbeat():void
		{
			trace('heartbeat at ' + gametimer.game_elapsed_time + 'ms');
			trace('player pos ' + player.pos_string());
			trace('player rot ' + player.rot_string());
			trace('camera ' + chase_camera.pos_string());
			trace('particles active: ' + particle_system.particles_active);
			trace('particles total: ' + particle_system.particles_created);
			trace('particles polies: ' + particle_system.totalpolycount);
			if (game_state == state_playing)
				trace('step: ' + (profiling_end - profiling_start) + 'ms');
		}
		
		private function enterFrame(e:Event):void
		{
			// clear scene before rendering is mandatory
			context3D.clear(0,0,0);
			// count frames, measure elapsed time
			gametimer.tick();
			// update all entities positions, etc
			game_step(gametimer.frame_ms);
			// render everything
			render_scene();
			// present/flip back buffer
			// now that all meshes have been drawn
			context3D.present();
			// update the FPS display
			fps_ticks++;
			var now:uint = getTimer();
			var delta:uint = now - fps_last;
			// only update the display once a second
			if (delta >= 1000)
			{
				var fps:Number = fps_ticks / delta * 1000;
				fps_tf.text = fps.toFixed(1) +
					" fps"
					+ "\n" + scene_polycount + " polies\n"
					//+ enemies.totalrendered + " enemies\n"
					//+ (enemy_bullets.totalrendered
					//+ player_bullets.totalrendered) + " bullets\n"
					//+ "[step=" + (profiling_end - profiling_start) + "ms]"
					;
				fps_ticks = 0;
				fps_last = now;
			}
			if (game_state == state_playing)
			{
				// update the rest of the GUI
				update_score();
			}
		}
		
		public function render():void
		{
			
		}
	}
}